﻿ using System.Collections.Generic;
using System.Windows.Forms;
using System.Xml;
using System;
using SdmxMl.Helper;
using SdmxMl.Common;

namespace SdmxMl.Structure
{
    /// <summary>
    /// The recursive CodeRef element is used to assemble the codes in the codelist(s)
    /// referenced by the parent hierarchical codelist into a hierarchy. The Level
    /// element is used to describe the levels of a levelled hierarchy, which may
    /// be referenced from each of the CodeRefs in the Hierarchy. A human-readable
    /// name must be assigned, which may be provided in multiple, parallel-language
    /// versions. A longer, human-readable Description may also be provided, which
    /// can also have multiple parallel-language versions. Annotations may be provided
    /// with the Annotations element. The id attribute provides a unique id for the
    /// hierarchy. The urn attribute can be used to specify the hierarchy with a valid
    /// SDMX Registry URN (see the SDMX Registry Specification). The version attribute
    /// specifies a version (understood to be "1.0" if not specified), and isFinal,
    /// once given a value of true, indicates that nothing may be changed without
    /// also changing the version number. validFrom and validTo are inclusive dates
    /// indicating the relevant period of the hierarchy.
    /// </summary>
    public class HierarchyType : BaseArtefact
    {
        #region Properties
        
        // Helper for Edit usage
        private CodelistRefTypeList _codelistRefList;
        public CodelistRefTypeList CodelistRefList
        {
            get { return _codelistRefList; }
        }

        // SDMX 2.0 only for validity interval, final and version
        public string ValidFrom { get; set; }
        public string ValidTo { get; set; }
        public bool IsFinal { get; set; }

        /// <summary>
        /// The urn attribute holds a valid SDMX Registry URN (see SDMX Registry Specification for details).
        /// </summary>
        public virtual string Urn { get; set; }

        /// <summary>
        /// The uri attribute holds a URI that contains a link to a resource with additional information
        /// about the object, such as a web page. This uri is not a SDMX message.
        /// </summary>
        public string Uri { get; set; }


        private CodeRefTypeList _codeRefList;
        public CodeRefTypeList CodeRefList
        {
            get { return _codeRefList; }
        }

        private LevelTypeList _levelList;
        public LevelTypeList LevelList
        {
            get { return _levelList; }
        }

        #endregion //Properties

        #region Constructors
        
        private void Init()
        {
            _levelList = new LevelTypeList();
            _codeRefList = new CodeRefTypeList();
        }

        public HierarchyType(CodelistRefTypeList clr)
        {
            _codelistRefList = clr;
            Init();

        }

        public HierarchyType(CodelistRefTypeList clr, XmlNode nodeArtefact, SdmxNamespaceManager nsmgr) 
            : base(nodeArtefact, nsmgr)
        {
            _codelistRefList = clr;
            Init();

            // 2.0 only
            ValidFrom = Xmlhelper.GetAtt(nodeArtefact, "validFrom");
            ValidTo = Xmlhelper.GetAtt(nodeArtefact, "validTo");
            IsFinal = Xmlhelper.GetAtt(nodeArtefact, "isFinal").ToLower() == "true";

            Urn = Xmlhelper.GetAtt(nodeArtefact, "urn");

            // 2.1 only
            Uri = Xmlhelper.GetAtt(nodeArtefact, "uri");

            string query ="./structure:Level";
            XmlNodeList nodeList = nodeArtefact.SelectNodes(query, nsmgr);
            if (nodeList.Count > 0)
            {
                if (nsmgr.VersionSdmx == SdmxVersion.SDMX_20)
                {
                    foreach (XmlNode nd in nodeList)
                    {
                        LevelType lv = new LevelType(nd, nsmgr);
                        _levelList.Add(lv);
                    }
                }
                else
                {
                    do
                    {
                    XmlNode nd = nodeList[0];
                    LevelType lv = new LevelType(nd, nsmgr);
                    _levelList.Add(lv);
                    nodeList = nd.SelectNodes(query, nsmgr);
                    }while (nodeList.Count > 0);
                }
            }

            _codeRefList = CodeRefType.ReadRefList(nodeArtefact, nsmgr);
        }

        #endregion //Constructors

        #region String representation

        public override string ToString()
        {
            return Id + " - " + Name.ToString();
        }
        #endregion

        #region XML Representation

        public void WriteXml(XmlTextWriter xw, string prefix, string tag)
        {
            if (SdmxNamespaceManager.SdmxTarget == SdmxVersion.SDMX_21)
                WriteSdmx_2_1(xw, prefix, tag);
            else // SDMX 2.0
                WriteSdmx_2_0(xw, prefix, tag);
        }

        private void WriteSdmx_2_1(XmlTextWriter xw, string prefix, string tag)
        {
            int aliasId = 1;
            bool manageLevels = LevelList.Count == CodelistRefList.Count;
            
            // Ensure not dummy 
            if (manageLevels == false)
                LevelList.Clear();

            xw.WriteStartElement(prefix, tag, null);

            // id required
            if (string.IsNullOrEmpty(Id))
                Id = "undefined";
            xw.WriteAttributeString("id", Id);
            Xmlhelper.WriteAttributeIfExist(xw, "urn", Urn);
            Xmlhelper.WriteAttributeIfExist(xw, "uri", Uri);
            if (manageLevels)
                Xmlhelper.WriteAttributeIfExist(xw, "leveled", manageLevels.ToString().ToLower());

            // Annotations, Name, Descr
            AnnotationList.WriteXml(xw, "common", null);
            Name.WriteXml(xw, prefix, "Name");
            Description.WriteXml(xw, prefix, "Description");

            // HierarchicalCode
            foreach (CodeRefType cr in CodeRefList)
                cr.WriteXml(xw, prefix, "CodeRef", ref aliasId, LevelList, 1);

            foreach (LevelType lv in LevelList)
                lv.WriteHierarchizedLevel_21(xw, prefix, "Level");
            foreach (LevelType lv in LevelList)
                xw.WriteEndElement(); 

            xw.WriteEndElement(); 
        }

        private void WriteSdmx_2_0(XmlTextWriter xw, string prefix, string tag)
        {
            int aliasId = 1;

            xw.WriteStartElement(prefix, tag, null);
            // id required
            if (string.IsNullOrEmpty(Id))
                Id = "undefined";
            xw.WriteAttributeString("id", Id);

            Xmlhelper.WriteAttributeIfExist(xw, "urn", Urn);
            if (Version != "1.0")
                Xmlhelper.WriteAttributeIfExist(xw, "version", Version);
            if (IsFinal)
                xw.WriteAttributeString("isFinal", "true");
            Xmlhelper.WriteAttributeIfExist(xw, "validFrom", ValidFrom);
            Xmlhelper.WriteAttributeIfExist(xw, "validTo", ValidTo);

            Name.WriteXml(xw, prefix, "Name");
            Description.WriteXml(xw, prefix, "Description");

            foreach (CodeRefType cr in CodeRefList)
                cr.WriteXml(xw, prefix, "CodeRef", ref aliasId, LevelList, 1);

            foreach (LevelType lv in LevelList)
                lv.WriteXml(xw, prefix, "Level");

            // Annotations
            AnnotationList.WriteXml(xw, prefix, null);

            xw.WriteEndElement();
        }


        #endregion // XML Representation

        #region Override

        public override TreeNode FillNode(TreeNode parentNode)
        {
            TreeNode t = base.FillNode(parentNode);

            return t;
        }

        #endregion // Override

        public void Sort(bool revert)
        {
            SortHierarchy(this.CodeRefList, revert);
        }

        // Recurse
        private void SortHierarchy(CodeRefTypeList crList, bool revert)
        {
            List<string> codes = new List<string>();
            foreach (CodeRefType crChild in crList)
            {
                codes.Add(crChild.CodeID);
                SortHierarchy(crChild.CodeRefList, revert);
            }
            bool numeric = NumHelper.IsListNumeric(codes, System.Globalization.NumberStyles.Number);
            if (numeric)
            {
                crList.Sort(delegate(CodeRefType c1, CodeRefType c2) { return (int)(Convert.ToInt64(c1.CodeID) - Convert.ToInt64(c2.CodeID)); });
            }
            else
            {
                crList.Sort(delegate(CodeRefType c1, CodeRefType c2) { return c1.CodeID.CompareTo(c2.CodeID); });
            }
            if (revert)
                crList.Reverse();

        }


        /// <summary>
        /// Sometimes levels are not described and if it possible to une more than one codelist refered 
        /// in the same level. So provide a calculation mechanism
        /// </summary>
        /// <returns>Max levels depth</returns>
        public int CalculateMaxLevelDepth()
        {
            int depth = 0;
            if (this.CodeRefList != null)
            {
                foreach (CodeRefType it in CodeRefList)
                    CalcDepth(it, ref depth, 1);
            }

            return depth;
        }

        private void CalcDepth(CodeRefType it, ref int depth, int level)
        {
            depth = System.Math.Max(depth, level);
            foreach (CodeRefType cr in it.CodeRefList)
                CalcDepth(cr, ref depth, level + 1);
        }

        public CodeRefType LocateCodeRef(CodeRefTypeList list, string codelistAlias, string codeId)
        {
            CodeRefType codeRef = null;
            foreach (CodeRefType cr in list)
            {
                if (cr.CodelistAliasRef == codelistAlias && cr.CodeID == codeId)
                    return cr;
                if ((codeRef = LocateCodeRef(cr.CodeRefList, codelistAlias, codeId)) != null)
                    return codeRef;
            }
            return null;
       }

    }

    /// <summary>
    /// Support collection of Hierarchy
    /// </summary>
    public class HierarchyTypeList : List<HierarchyType>
    {

    }
}
